﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Utility.Math
{
	/// <summary>
	/// 2d roation with only 4 direction
	/// 0: (0, 1) +y
	/// 1: (1, 0) +x
	/// 2: (0, -1) -y
	/// 3: (-1, 0) -x
	/// </summary>
	[Serializable]
	public struct Rotation4 : IEquatable<Rotation4>
	{
		public enum Dir
		{
			Forward = 0,
			Right = 1,
			Backward = 2,
			Left = 3,
		}

		public static Rotation4 FromTo_Pos (Vector2Int from, Vector2Int to) => new Rotation4 (to - from);
		public static Rotation4 FromTo_Pos (Vector2 from, Vector2 to) => new Rotation4 (to - from);

		public static Rotation4 Identity => new Rotation4 (Dir.Forward);
		public static Rotation4 Forward => new Rotation4 (Dir.Forward);
		public static Rotation4 Backward => new Rotation4 (Dir.Backward);
		public static Rotation4 Left => new Rotation4 (Dir.Left);
		public static Rotation4 Right => new Rotation4 (Dir.Right);

		Rotation4 (int i)
		{
			this.dir = (Dir)i.Clamp (0, 4, MathEX.ClampType.Periodic);
		}

		public Rotation4 (Dir dir)
		{
			this.dir = dir;
		}

		public Rotation4 (float angle)
		{
			angle = angle.Clamp (0, 360, MathEX.ClampType.Periodic);
			if (angle < 45)
			{
				dir = Dir.Forward;
			}
			else if (angle < 135)
			{
				dir = Dir.Right;
			}
			else if (angle < 225)
			{
				dir = Dir.Backward;
			}
			else if (angle < 315)
			{
				dir = Dir.Left;
			}
			else
			{
				dir = Dir.Forward;
			}
		}

		public Rotation4 (Vector2 vec, Vector2 forward) : this (forward.AngleTo (vec)) { }
		public Rotation4 (Vector2 vec) : this (vec, Vector2.up) { }
		public Rotation4 (Vector2Int vec, Vector2Int forward) : this (forward.AngleTo (vec)) { }
		public Rotation4 (Vector2Int vec) : this (vec, Vector2Int.up) { }


		[SerializeField]
		Dir dir;
		public Dir Direction => dir;
		public int DirIndex => (int)dir;

		/// <summary>
		/// up is 0, increase clockwise
		/// </summary>
		public float Angle => (int)dir * 90;

		public static explicit operator Rotation4 (int i)
		{
			return new Rotation4 ((Dir)i);
		}

		public static implicit operator Rotation4 (Dir dir)
		{
			return new Rotation4 (dir);
		}

		public static implicit operator Dir (Rotation4 rot)
		{
			return rot.dir;
		}


		//rotate vector2 by orientation's angle
		public static Vector2 operator * (Rotation4 orientation, Vector2 vec2)
		{
			switch (orientation.dir)
			{
				default:
				case Dir.Forward:
					return vec2;
				case Dir.Right:
					return new Vector2 (vec2.y, -vec2.x);
				case Dir.Backward:
					return -vec2;
				case Dir.Left:
					return new Vector2 (-vec2.y, vec2.x);
			}
		}

		public static Vector2Int operator * (Rotation4 orientation, Vector2Int vec2)
		{
			switch (orientation.dir)
			{
				default:
				case Dir.Forward:
					return vec2;
				case Dir.Right:
					return new Vector2Int (vec2.y, -vec2.x);
				case Dir.Backward:
					return vec2 * -1;
				case Dir.Left:
					return new Vector2Int (-vec2.y, vec2.x);
			}
		}

		//stack rotation
		public static Rotation4 operator * (Rotation4 rot, int count)
		{
			return new Rotation4 (rot.DirIndex * count);
		}

		public static Rotation4 operator * (int count, Rotation4 rot)
		{
			return new Rotation4 (rot.DirIndex * count);
		}

		//inverse
		public static Rotation4 operator - (Rotation4 a)
		{
			return new Rotation4 (-a.DirIndex);
		}

		//addition
		public static Rotation4 operator + (Rotation4 a, Rotation4 b)
		{
			return new Rotation4 (a.DirIndex + b.DirIndex);
		}
		public static Rotation4 operator - (Rotation4 a, Rotation4 b)
		{
			return new Rotation4 (a.DirIndex - b.DirIndex);
		}

		//equal
		public static bool operator == (Rotation4 a, Rotation4 b)
		{
			return a.dir == b.dir;
		}
		public static bool operator != (Rotation4 a, Rotation4 b)
		{
			return a.dir != b.dir;
		}

		public override bool Equals (object obj)
		{
			if (!(obj is Rotation4))
			{
				return false;
			}

			var orientation = (Rotation4)obj;
			return dir == orientation.dir;
		}

		public override int GetHashCode ()
		{
			return -1584136870 + dir.GetHashCode ();
		}

		public override string ToString ()
		{
			return dir.ToString ();
		}

		public string GetArrow ()
		{
			switch (dir)
			{
				case Dir.Forward:
					return "↑";
				case Dir.Right:
					return "→";
				case Dir.Backward:
					return "↓";
				case Dir.Left:
					return "←";
				default:
					return "·";
			}
		}

		bool IEquatable<Rotation4>.Equals (Rotation4 other)
		{
			return dir == other.dir;
		}
	}

}